﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Threading;
using NLog;

namespace DeSleeper.Client.Framework
{
    public class ViewModelBase : INotifyPropertyChanged
    {
        private readonly Logger _logger = LogManager.GetCurrentClassLogger();
        protected Logger Logger { get { return _logger; } }

        [Import]
        public ICommandMessageProvider MessageProvider { get; set; }

        // TODO: Remove this after composition support is implemented.
        private readonly static ICommandMessageProvider DefaultMessageProvider = new DefaultMessageProvider();
        public ICommand AcknowledgeMessageCommand { get; private set;}

        public ViewModelBase()
        {
            MessageProvider = DefaultMessageProvider;
            AcknowledgeMessageCommand = new DelegateCommand(parameter =>
                                                            {
                                                                var message = parameter as CommandMessage;
                                                                if (message != null)
                                                                    MessageProvider.Acknowledge(message);
                                                            });
        }
        
        public event EventHandler CommandStarting;
        public event RunWorkerCompletedEventHandler CommandCompleted;

        #region INotifyPropertyChanged Implementation
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string name)
        {
            OnPropertyChanged(new PropertyChangedEventArgs(name));
        }

        protected void OnPropertyChanged(PropertyChangedEventArgs args)
        {
            if (PropertyChanged != null)
            {
                if (Dispatcher.CurrentDispatcher.CheckAccess())
                {
                    PropertyChanged(this, args);
                }
                else
                {
                    Dispatcher.CurrentDispatcher.Invoke(new Action(() => PropertyChanged(this, args)));
                }
            }
        }
        #endregion

        protected void SetCommandEvents(AsyncCommand command)
        {
            command.Starting += commandStarting;
            command.Completed += commandComplete;
        }

        private void commandStarting(object sender, EventArgs e)
        {
            var command = sender as AsyncCommand;
            if (command != null)
            {
                MessageProvider.AddStartMessage(command);
            }

            if (CommandStarting != null)
                CommandStarting(sender, e);
        }

        private void commandComplete(object sender, RunWorkerCompletedEventArgs e)
        {
            var command = sender as AsyncCommand;
            if (command == null)
            {
                // This isn't really possible since we only bind this method via SetCommandEvents.
                MessageProvider.AddMessage(new CommandMessage(command, CommandMessageType.Error, "Invalid Command Executed."));
                return;
            }

            if (e.Error == null)
                handleSuccess(command);
            else
                handleException(command, e.Error);

            if (CommandCompleted != null)
                CommandCompleted(sender, e);
        }

        private void handleSuccess(AsyncCommand command)
        {
            MessageProvider.AddCompleteMessage(command);
        }

        private void handleException(AsyncCommand command, Exception ex)
        {
            Logger.LogException(LogLevel.Error, command.FailureMessageFormat, ex);
            MessageProvider.AddError(command, ex);
        }

        protected void InitializePropertyAsync<T>(Func<T> valueSelector, Action<T> setResult, string propertyName)
        {
            var uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
            var getTask = Task.Factory.StartNew(valueSelector);
            getTask.ContinueWith(result => MessageProvider.AddError(null, result.Exception), new CancellationToken(), TaskContinuationOptions.OnlyOnFaulted, uiTaskScheduler);
            getTask.ContinueWith(task =>
                                 {
                                     setResult(task.Result);
                                     OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
                                 },
                                 new CancellationToken(), TaskContinuationOptions.NotOnFaulted, uiTaskScheduler);
        }

        protected void InitializeCollectionAsync<T>(Func<IEnumerable<T>> valueSelector, ObservableCollection<T> collection)
        {
            var currentDispatcher = Dispatcher.CurrentDispatcher;
            var getTask = Task.Factory.StartNew(() =>
                                                {
                                                    foreach (var item in valueSelector())
                                                    {
                                                        var localItem = item;
                                                        currentDispatcher.BeginInvoke(new Action(() => collection.Add(localItem)));
                                                    }
                                                });

            var uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
            getTask.ContinueWith(result => MessageProvider.AddError(null, result.Exception), new CancellationToken(), TaskContinuationOptions.OnlyOnFaulted, uiTaskScheduler);
        }
    }
}
